/* em/em.c 
 * 
 * This file is part of em. 
 * 
 * em is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version. 
 * 
 * em is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with em. If not, see <https://www.gnu.org/licenses/>
 */ 




#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <klist.h>
#include <em/em.h>


struct klist_node words = KLIST_NODE_INIT(&words);
struct klist_node macros = KLIST_NODE_INIT(&macros);
struct klist_node out = KLIST_NODE_INIT(&out);

#define OUT 	1
#define IN 	2


char *skip_spaces(char *s) {
	for (; isspace(*s); s++) 
		;

	return s;
}

int getwordtype(char c) {
	if (isdigit(c)) 
		return WORD_DIGIT;

	else if (isalpha(c)) 
		return WORD_NAME;

	else if (c == '"') 
		return WORD_STR;

	else if (isspace(c))
		return WORD_SPACE;

	else 
		return WORD_OP;
}

int join_word(char *s, char *e) {
	struct word *w;

	if (unlikely(!(w = malloc(sizeof(struct word))))) {
		fprintf(stderr, "*** malloc() failed\n");

		abort();
	}

	klist_init(&w->node);
	w->p = s;
	w->len = e - s;
	w->type = getwordtype(*s);
	w->marked = 0;

	klist_add(&words, &w->node);

	return 0;
}

int scan(char *s, int size) {
	int stat = OUT, curr = WORD_INVAL;
	char c, *p = s, *e = s + size;

	for (; s < e; s++) {
		c = *s;

		if (c == '"') {
			if (stat == OUT) {
				stat = IN;

				continue;
			}

			else {
				stat = OUT;
				join_word(p, s + 1);
				p = skip_spaces(s + 1);
				s = p - 1;

				curr = WORD_INVAL;

				continue;
			}
		}

		if (stat == IN) 
			continue;

		/* first time to this loop */

		if (curr == WORD_INVAL) {
			curr = getwordtype(c);

			continue;
		}

		if (curr == WORD_OP) {
			join_word(p, s);
			p = skip_spaces(s);
			s = p - 1;

			curr = WORD_INVAL;
			continue;
		}

		if (curr != getwordtype(c)) {
			if (curr == WORD_NAME && isdigit(c)) 
				continue;

			join_word(p, s);	
			p = skip_spaces(s);
			s = p - 1;

			curr = WORD_INVAL;
			
			continue;
		}
	}

	return 0;
}


struct word *word_of(struct klist_node *n) {
	struct word *w;

	if (unlikely(!(w = container_of(n, struct word, node)))) {
		fprintf(stderr, "em: word required at the end of file. \n");

		abort();
	}

	return w;
}

#define btoi(c) 	((c) - '0')


long getnumber(struct word *w) {
	char *p;
	long res, exp;

	res = 0;
	exp = 1;

	p = w->p + w->len - 1;

	while (w->p <= p) {
		res += btoi(*p) * exp;
		exp *= 10;

		p--;
	}

	return res;
}


#define next_word() 	\
do {			\
	n = n->next;	\
	w = word_of(n);	\
} while (0);


struct klist_node *parse_pattern(struct klist_node *node, struct mpattern *dst) {
	struct word *w;
	struct klist_node *n;

	n = node;
	w = word_of(n);
	
	/* dir */
	if (unlikely(w->type != WORD_OP)) {
		fprintf(stderr, "em: an direction required. \n");
		abort();
	}

	switch (*w->p) {
	case '+':
		dst->dir = MPATTERN_GT;
		break;

	case '-':
		dst->dir = MPATTERN_BL;
		break;

	case '=':
		dst->dir = MPATTERN_EQU;
		break;

	case '?':
		dst->dir = MPATTERN_ANY;
		break;

	default:
		fprintf(stderr, "em: invalid direction qualifier. \n");
		abort();
	}

	next_word();

	if (unlikely(w->type != WORD_DIGIT)) {
		fprintf(stderr, "em: number required. \n");
		abort();
	}

	dst->nr = getnumber(w);
	next_word();

	if (unlikely(w->type != WORD_NAME)) {
		fprintf(stderr, "em: a type qualifier required. \n");
		abort();
	}

	switch (*w->p) {
	case 's':
		dst->type = WORD_STR;
		break;

	case 'i':
		dst->type = WORD_DIGIT;
		break;

	case 'n':
		dst->type = WORD_NAME;
		break;

	case 'e':
		dst->type = WORD_INVAL;
		break;

	default:
		fprintf(stderr, "em: invalid type qualifier (c =( {s, i, n, e})");
		abort();
	}

	return n->next;
}

int get_macro_body_len(struct klist_node *node) {
	struct word 		*w;
	struct klist_node 	*n;
	int 			stat = OUT, 
				levels = 1, 
				i = 0;

	for (n = node; n != &words && levels; n = n->next) {
		w = word_of(n);

		if (stat == IN) {
			if (!strncmp(w->p, "em", 2) && w->len == 2) 
				--levels;

			else if (*w->p == 'm' && w->len == 1) 
				++levels;
			continue;
		}

		else if (*w->p == '#') {
			stat = IN;

			continue;
		}

		i++;
	}

	if (levels) {
		fprintf(stderr, "em: word required at the end of file. \n");
		abort();
	}

	return i;
}


int parse_macro(struct klist_node *node) {
	struct klist_node *n;
	struct word *w;
	struct macro *macro;

	/* for next_word() */
	n = node;

	/* get macro name */

	if (unlikely(!(macro = malloc(sizeof(struct macro))))) {
		fprintf(stderr, "*** malloc() failed\n");

		return 1;
	}

	klist_init(&macro->node);
	next_word();

	/* an operator or number etc can also be the name of a macro */

#if 0
	if (w->type != WORD_NAME) {
		fprintf(stderr, "em: word type `WORD_NAME' required. \n");

		abort();
	}
#endif

	macro->name = strndup(w->p, w->len);
	macro->len = w->len;
	next_word();

	/* ( */

	if (*w->p != '(') {
		fprintf(stderr, "em: `(' required. \n");
		abort();
	}

	next_word();
	n = parse_pattern(n, macro->patterns + MACRO_PATT_LEFT);

	/* skip comma */
	next_word();
	n = parse_pattern(n, macro->patterns + MACRO_PATT_RIGHT);
	w = word_of(n);

	if (*w->p != ')') {
		fprintf(stderr, "em: `)' required. \n");
		abort();
	}

	next_word();
	macro->body = word_of(n);
	macro->body_len = get_macro_body_len(n);
	klist_add(&macros, &macro->node);
	
	return 0;
}


int check_macros(void) {
	struct klist_node *n;
	struct word *w;
	int stat = OUT, i = 0;

	for (n = words.next; n != &words; n = n->next) {
		w = word_of(n);

		if (stat == IN) {
			if (*w->p == 'm' && w->len == 1) {
				parse_macro(n);
				i++;
			}

			stat = OUT;
		}

		if (*w->p == '#') {
			stat = IN;

			continue;
		}
	}

	printf("%d macro%s defined. \n", i, i > 1 ? "s" : "");

	return 0;
}

/*int pattern(struct mpattern *pattern, struct word *w, int left, int right) {
}*/

int words_left(struct word *word) {
	struct klist_node 	*n;
	struct word 		*w;
	int 			i = 0;

	n = &word->node;

	do {
		n = n->prev;
		w = word_of(n);

		i++;
	} while (!w->marked && *w->p != SPLIT && n != &words);

	/* at the last loop, we incremented the i */
	return i - 1;
}

int words_right(struct word *word) {
	struct klist_node 	*n;
	struct word 		*w;
	int 			i = 0;

	n = &word->node;

	do {
		n = n->next;
		w = word_of(n);

		i++;
	} while (!w->marked && *w->p != SPLIT && n != &words);

	return i - 1;
}


int nr_type_same(int type, struct word *word, int dir) {
	struct klist_node 	*n;
	struct word 		*w;
	int 			i = 0;

	n = &word->node;

	do {
		if (dir == MACRO_PATT_LEFT) 
			n = n->prev;
		else 
			n = n->next;

		w = word_of(n);
		i++;
	} while (!w->marked && *w->p != SPLIT && n != &words && w->type == type);

	return i - 1;
}


int __check_pattern(struct mpattern *p, struct word *word, int nr, int dir) {
	int same = nr_type_same(p->type, word, dir);

	switch (p->dir) {
	case MPATTERN_GT:
		if (nr <= p->nr) 
			return 0;

		if (same <= p->nr) 
			return 0;

		break;

	case MPATTERN_BL:
		if (nr >= p->nr) 
			return 0;

		if (same >= p->nr) 
			return 0;

		break;

	case MPATTERN_EQU:
		if (nr != p->nr) 
			return 0;

		if (same != p->nr) 
			return 0;

		break;
	}

	return 1;
}


int check_pattern(struct mpattern *patterns, struct word *w, int left, int right) {
	/* first check on the left */
	if (!__check_pattern(patterns + MACRO_PATT_LEFT, w, left, MACRO_PATT_LEFT)) 
		return 0;

	if (!__check_pattern(patterns + MACRO_PATT_RIGHT, w, right, MACRO_PATT_RIGHT)) 
		return 0;

	return 1;
}

int outputs(char *s) {
	struct output_tp *p;

	if (unlikely(!(p = malloc(sizeof(struct output_tp))))) {
		fprintf(stderr, "*** malloc() failed\n");
		abort();
	}

	klist_init(&p->node);

	p->p = strndup(s, strlen(s));
	p->type = OUTPUT_STRP;
	klist_add(&out, &p->node);

	return 0;
}


int output(struct word *w) {
	struct output_tp *p;

	if (unlikely(!(p = malloc(sizeof(struct output_tp))))) {
		fprintf(stderr, "*** malloc() failed\n");
		abort();
	}

	klist_init(&p->node);
	p->word = w;
	p->type = OUTPUT_WORD;

	klist_add(&out, &p->node);

	return 0;
}

int flush_output(void) {
	struct klist_node *n;
	struct output_tp *p;
	char *s;

	for (n = out.next; n != &out; n = n->next) {
		p = container_of(n, struct output_tp, node);

		if (p->type == OUTPUT_STRP) 
			printf("%s ", p->p);

		else {
			s = strndup(p->word->p, p->word->len);
			printf("%s ", s);
			free(s);
		}
	}

	putc('\n', stdout);
#if 0
	fflush(stdout);
#endif

	return 0;
}

int output_remove(struct word *w) {
	struct klist_node *n;
	struct output_tp *p;

	for (n = out.next; n != &out; n = n->next) {
		p = container_of(n, struct output_tp, node);

		if (p->type == OUTPUT_STRP) 
			continue;

		if (!strcmp(p->word->p, w->p)) {
			klist_del(&out, n);
			free(p->word);
			free(p);

			return 0;
		}
	}

	fprintf(stderr, "*** output_remove: not found. \n");
	abort();

	return 114514;
}

struct macro *is_macro(struct word *w) {
	struct klist_node 	*n;
	struct macro 		*m;
	int 			left, 
				right;

	left = words_left(w);
	right = words_right(w);

	for (n = macros.next; n != &macros; n = n->next) {
		m = container_of(n, struct macro, node);

		if (!strncmp(m->name, w->p, w->len) && w->len == m->len 
				&& check_pattern(m->patterns, w, left, right)) 
			return m;
	}

	return NULL;
}


struct word *nth_arg(struct word *word, long nth, int dir) {
	struct klist_node 	*n;
	struct word 		*w, *p;

	n = &(w = word)->node;

	while (nth--) {
		if (dir) 
			n = n->prev;
		else 
			n = n->next;
	}

	mark_word((p = word_of(n)));
	if (dir) 
		output_remove(p);

	return p;
}


int macro_expand(struct word *word, struct macro *m) {
	struct klist_node 	*n;
	struct word 		*w, *p;
	/* '\*' */
#define DIRECT 			3
	int 			i = m->body_len, stat = OUT, sign = 0;

	(void) word;

	n = &(w = m->body)->node;

	while (i--) {
		switch (*w->p) {
		case '*':
			if (stat == OUT) 
				stat = IN;
			
			switch (stat) {
			case OUT:
				stat = IN;
				break;

			case DIRECT:
				stat = OUT;
				outputs("*");

				break;
			}

			break;

		case '\\':
			switch (stat) {
			case DIRECT:
				stat = OUT;
				outputs("\\");

				break;

			default:
				stat = DIRECT;
			}

			break;

		default:
			switch (stat) {
			case OUT:
				output(w);
				break;

			case IN:
				/* it's the sign */
				switch (*w->p) {
				case '+':
					sign = 0;
					break;

				case '-':
					sign = 1;
					break;

				default:
					fprintf(stderr, "em: invalid sign. \n");
					abort();
				}

				next_word();
				p = nth_arg(word, getnumber(w), sign);
				output(p);
				i--;

				break;
			}

			stat = OUT;
			break;
		}

		next_word();
	}

	return 0;
}

int expand(void) {
	struct klist_node 	*n;
	struct word 		*w;
	struct macro 		*m;

	/* times of macro define (not a reference) */
	int 			levels = 0, 
	/* #  */
				stat = OUT, 
				i = 0;

	for (n = words.next; n != &words; n = n->next) {
		/* word_of aborts at the end of file */
		w = container_of(n, struct word, node);

		if (stat == IN) {
			if (*w->p == 'm' && w->len == 1) 
				++levels;

			else if (!strncmp(w->p, "em", 2) && w->len == 2) 
				--levels;

			stat = OUT;
			continue;
		}

		else if (*w->p == '#') {
			stat = IN;
			continue;
		}

		if (levels) 
			continue;

		if (builtin(w)) {
			next_word();
			i++;

			continue;
		}

		if ((m = is_macro(w))) {
			macro_expand(w, m);
			i++;
			next_word();
		}

		else 
			output(w);
	}

	printf("%d macro%s expanded. \n", i, i > 1 ? "s" : "");
	flush_output();

	return 0;
}


